package com.lcifn.springmvc.controller;

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.junit.Test;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.util.UriComponentsBuilder;

import com.fasterxml.jackson.annotation.JsonView;
import com.lcifn.springmvc.bean.User;

import sun.security.provider.certpath.OCSPResponse.ResponseStatus;

@Controller
public class ExampleController {
	
	private ModelAndView mav = new ModelAndView("example/example");
	
	@RequestMapping("/example")
	public ModelAndView index(){
		return new ModelAndView("example/example");
	}

	// @PathVariable  路径变量
	@RequestMapping("/path/{pathVar}")
	public ModelAndView pathVariable(@PathVariable String pathVar, @PathVariable("pathVar") Integer intVar){
		mav.addObject("msg", "pathVar:" + pathVar + ",intVar:" + intVar);
		return mav;
	}
	
	// @MatrixVariable 矩阵变量
	@RequestMapping("/matrix/{id}")
	public ModelAndView matrixVariable(@MatrixVariable(value="q") int q){
		mav.addObject("msg", "matrix:" + q);
		return mav;
	}
	
	// multi matrix variables
	@RequestMapping("/matrix/multi/{id}")
	public ModelAndView multiMatrixVariable(@MatrixVariable Map<String, String> matrixVars){
		mav.addObject("msg", "matrix:" + matrixVars);
		return mav;
	}
	
	// consumes 
	// use postman request
	@RequestMapping(value = "/consumes", consumes="application/json")
	public ModelAndView consumes(HttpServletRequest req){
		mav.addObject("msg", "consumes:" + req.getContentType());
		return mav;
	}
	
	// produces
	@RequestMapping(value = "/produces", produces="application/json")
	@ResponseBody
	public String produces(HttpServletRequest req){
		return "produces:" + req.getHeader("accept");
	}
	
	// request param
	// http://localhost:8080/springmvc/request/param?myParam=1
	@RequestMapping(value = "/request/param", params="myParam")
	public ModelAndView requestParam(HttpServletRequest req){
		mav.addObject("msg", "request params:" + req.getParameter("myParam"));
		return mav;
	}
	
	// request header
	// header:  myHeader = 1
	@RequestMapping(value = "/request/header", headers="myHeader")
	public ModelAndView requestHeader(HttpServletRequest req){
		mav.addObject("msg", "request params:" + req.getHeader("myHeader"));
		return mav;
	}
	
	// @RequestParam 将请求参数绑定到方法参数
	// http://localhost:8080/springmvc/request?myParam=abc
	@RequestMapping("/request")
	public ModelAndView param(@RequestParam("myParam") String myParam){
		mav.addObject("msg", "@RequestParam  myParam:" + myParam);
		return mav;
	}
	
	// @RequestBody 表示该参数将与HTTP请求体绑定
	// 使用post请求，需要有请求体
	// 默认required属性为true，则必须有请求体
	// HttpMessageConverter负责将请求信息转换为指定对象和将指定对象转换为请求体
	// RequestMappingHandlerAdapter 默认支持 @RequestBody 使用以下 HttpMessageConverters
	// ByteArrayHttpMessageConverter 转换为字节数组
	// StringHttpMessageConverter 转换为字符串
	// FormHttpMessageConverter 表单数据与 MultiValueMap<String, String> 互转
	// SourceHttpMessageConverter 表单数据与 javax.xml.transform.Source 互转
	@RequestMapping("/requestBody")
	public void requestBody(@RequestBody(required=false) String body, Writer writer) throws IOException{
		writer.write(body);
	}
	
	// @ResponseBody 表示直接将返回数据写到 HTTP 响应体里
	// 使用 HttpMessageConverter 将返回对象转换为响应体
	// @RestController 默认使用@ResponseBody
	// 转json需要依赖jackson-databind (MappingJackson2HttpMessageConverter)
	// 转xml需要依赖jackson-dataformat-xml (MappingJackson2XmlHttpMessageConverter) 
	// 默认转xml优先级高于json，可以通过produces指定
	@RequestMapping(value="/responseBody", produces="application/json")
	@ResponseBody
	public Map<String, Object> responseBody(){
		Map<String, Object> retMap = new HashMap<>();
		retMap.put("param1", "abc");
		return retMap;
	}
	
	// HttpEntity与ResponseEntity
	// 也是通过HttpMessageConverter来转换
	@RequestMapping("/httpEntity")
	public ResponseEntity<byte[]> httpEntity(HttpEntity<Map<String,String>> requestEntity){
		String acceptHeaderValue = requestEntity.getHeaders().getFirst("Accept");
		Map<String, String> body = requestEntity.getBody();
		
		HttpHeaders responseHeaders = new HttpHeaders();
		responseHeaders.add("responseHeader", acceptHeaderValue);
		return new ResponseEntity<byte[]>(body.toString().getBytes(Charset.forName("utf-8")),
				responseHeaders, HttpStatus.OK);
	}
	
	// 方法上使用@ModelAttribute 
	// 第一种，在方法里隐式添加一个属性并返回；第二种，方法里接收 Model 参数，并将任意个属性添加到 Model中。
	// 目的在于添加一个或多个模型属性, 返回的模型可以通过attribute的key在页面获取
	// 所支持的参数类型与 @RequestMapping一样, 不同的是，其不能直接映射到 request
	// 在同一个控制器里，@ModelAttribute 会在 @RequestMapping 之前调用
	//@RequestMapping("/modelAttribute/method")
	//@ModelAttribute
	public void modelAttributeOnMethod(Model model){
		User user = new User("zhangsan", 20);
		model.addAttribute("user", user);
	}
	
	// 方法参数上使用@ModelAttribute,表示该参数会从模型中检索  Spring MVC 数据绑定
	// 如果该参数不在模型中，此参数会先执行初始化或后添加到模型里；如果参数存在模型里，此参数字段会填充所有匹配其名称的请求参数。
	// 能接受URL后的参数及表单参数
	@RequestMapping("/modelAttribute/parameter")
	@ResponseBody
	public String modelAttributeOnParameter(@ModelAttribute User user){
		return user.toString();
	}
	
	// @SessionAttribute获取session中已存在的属性，可以是从@SessionAttributes
	// @RequestAttribute获取request已存在的属性，可以是filter/interceptor中设置的
	@RequestMapping("/request/attribute")
	public ModelAndView requestAttribute(@RequestAttribute User user){
		mav.addObject("msg", user.toString());
		return mav;
	}
	
	// 处理HTTP PUT提交表单数据(application/x-www-form-urlencoded)
	// 因为 Servlet 规范要求 ServletRequest.getParameter*() 方法需要支持表单属性访问，
	// 而这只适用于 HTTP POST，不适用于 HTTP PUT
	// 支持 HTTP PUT 和 PATCH 请求, 增加HttpPutFormContentFilter
	@RequestMapping(value="/form/put", method = RequestMethod.PUT)
	@ResponseBody
	public String httpPutForm(@ModelAttribute User user){
		return user.toString();
	}
	
	// @CookieValue
	// 将方法参数与HTTP cookie 值绑定
	@RequestMapping("/cookie")
	@ResponseBody
	public String cookie(@CookieValue("JSESSIONID") String sessionId){
		return "jsessionid:" + sessionId;
	}
	
	// @RequestHeader
	// 将方法参数与请求头字段绑定
	// 支持将逗号分隔的字符串转换为数组/集合
	@RequestMapping("/requestHeader")
	@ResponseBody
	public String requestHeader(@RequestHeader("Accept") List<String> accept,
			@RequestHeader("Accept-Encoding") String encoding){
		return "Accept:" + accept + ", Accept-Encoding:" + encoding;
	}
	
	// @InitBinder
	// 配置见AdviceController
	// http://localhost:8080/springmvc/initBinder/date?date=2017-09-22
	@RequestMapping("/initBinder/date")
	@ResponseBody
	public String initBinderDate(@RequestParam("date") Date date){
		return date.toString();
	}
	
	// Last-Modified处理
	// 请求头 If-Modified-Since = 1
	@RequestMapping("/lastModified")
	public String lastModified(WebRequest request){
		long lastModified = 1;
		
		if(request.checkNotModified(lastModified)){
			return null;
		}
		
		return "";
	}
	
	// @JsonView  Jackson 序列化视图
	// 将对象序列化到响应体时，偶尔需要过滤敏感的数据
	@RequestMapping(value="/jsonview", produces="application/json")
	@JsonView(User.WithoutAgeView.class)
	@ResponseBody
	public User jsonView(){
		return new User("zhangsan", 22);
	}
	
	
	// 1.基于 Servlet 3 的异步请求
	// 在异步请求中，控制器方法通常会返回 java.util.concurrent.Callable 对象
	// 再使用一个独立的线程产生返回值，而不是直接返回一个值。
	// 同时释放 Servlet 容器的主线程和允许处理其他请求。
	// Spring MVC 借助 TaskExecutor ，在一个独立线程中调用 Callable，
	// 当 Callable 返回时，将请求转发到 Servlet 容器并继续处理 Callable 返回值。
	// 另一种异步方式是通过返回DeferredResult
	// 将DeferredResult保存到内存队列，并通过一个独立线程处理，然后执行DeferredResult.setResult(data)触发返回
	// 2.异步请求的异常通过@ExceptionHandler或HandlerExceptionResolver实例来处理
	// 3.拦截异步请求:
	// 可以通过AsyncHandlerInterceptor实例实现
	// DeferredResult通过onTimeout和onCompletion处理超时和完成
	// Callable可以封装成WebAsyncTask
	// 注册全局性的 CallableProcessingInterceptor 或 DeferredResultProcessingInterceptor
	@RequestMapping("/callable")
	public Callable<ModelAndView> async(){
		String mainThreadName = Thread.currentThread().getName();
		
		return new Callable<ModelAndView>() {

			@Override
			public ModelAndView call() throws Exception {
				String childThreadName = Thread.currentThread().getName();
				mav.addObject("msg", "mainThread:" + mainThreadName + ",childThread:" + childThreadName);
				return mav;
			}
		};
	}
	
	// 重定向：RedirectView或redirect:前缀
	// 通过Redirect
	@RequestMapping("/redirect")
	public ModelAndView redirect(RedirectAttributes attrs){
		attrs.addAttribute("msg", "redirectMsg");
		//mav.addObject("msg", "redirectMsg");
		mav.setView(new RedirectView("example"));
		return mav;
	}
	
	// redirect:
	@RequestMapping("/redirect/prefix")
	public String redirectPrefix(){
		return "redirect:../user/get";
	}
	
	// forward:
	@RequestMapping("forward")
	public String forward(){
		return "forward:user/get";
	}
	
	// 构建URI
	@RequestMapping("/uri")
	public void buildURI(HttpServletResponse resp) throws IOException{
		String uri = UriComponentsBuilder.newInstance().scheme("http")
			.host("example.com").path("/hotel/{hotel}/booking/{booking}")
			.build().expand("12","13").encode().toUriString();
		System.out.println(uri);
		
		String servletUri = ServletUriComponentsBuilder.fromCurrentRequest().path("/example")
			.build().toUriString();
		
		System.out.println(servletUri);
		
		String mvcUri = MvcUriComponentsBuilder.fromMethodName(ExampleController.class, "pathVariable", "123", 123)
			.buildAndExpand(234).toUriString();
		System.out.println(mvcUri);
		if(1==1)
			throw new IllegalArgumentException();
		resp.getWriter().flush();
	}
	
	
}
